;;; trial.lisp

;;; Implement the basic logic for a trial: present a stimulus, allow
;;; the subject to enter an answer, show feedback, show crosshair.

(defpackage :trial
  (:use :cl)
  (:export actions
           events
           trial
           draw-stimulus
           draw-input
           draw-keypad
           draw-feedback
           draw-feedback-input
           log-start
           stimulus
           input keypad
           layout
           correct-p))

(in-package :trial)

(defclass trial (exp:state)
  ((stimulus :initarg :stimulus :accessor stimulus)
   (input :initarg :input :accessor input)
   (keypad :initarg :keypad :accessor keypad)
   (layout :accessor layout)
   (timeout :initform nil :initarg :timeout :accessor timeout)
   (feedback-timeout :initform 5 :initarg :feedback-timeout :accessor feedback-timeout)))

;; The API
(defgeneric draw-stimulus (exp trial))
(defgeneric draw-input (exp trial))
(defgeneric draw-keypad (exp trial))
(defgeneric draw-feedback (exp trial correct-p))
(defgeneric draw-feedback-input (exp trial correct-p))
(defgeneric correct-p (exp trial))
(defgeneric log-start (exp state))

(defmethod exp:state-init (experiment (state trial))
  (log-start experiment state)
  (setf (layout state)
        (make-instance 'gwee:column-layout
                       :parent state
                       :padding 10
                       :subviews (list (setf (stimulus state) (draw-stimulus experiment state))
                                       (setf (input state) (draw-input experiment state))
                                       (setf (keypad state) (draw-keypad experiment state)))))
  (when (timeout state)
    (exp:start-timer (timeout state))))

(exp:define-event-match keypad ((state trial)
                                (event exp:key-press-event))
  (let ((map '(:sdl-key-0 #\0 :sdl-key-1 #\1 :sdl-key-2 #\2
               :sdl-key-3 #\3 :sdl-key-4 #\4 :sdl-key-5 #\5
               :sdl-key-6 #\6 :sdl-key-7 #\7 :sdl-key-8 #\8
               :sdl-key-9 #\9 :sdl-key-minus #\-)))
    (getf (gwee:key-sym (exp:key-press-event-key event)))))

(exp:define-action keypad-input ((state trial)
                                 (keypad))
  (exp:log experiment 'input :digit (string keypad))
  (input-widget:input-add (input state) keypad))

(exp:define-action submit ((state trial)
                           (exp:keypress :char #\Return))
  "Show feedback and move on."
  (multiple-value-bind (done input phase) (input-widget:input-submit (input state))
    (if done
        (let* ((correct (correct-p experiment state)))
          (setf (gwee:view-subviews (layout state))
                (list
                 (draw-feedback experiment state correct)
                 (draw-feedback-input experiment state correct)
                 ;; we need a dummy keypad view to keep the view as similar as possible
                 (make-instance 'gwee:view :width (gwee:view-width (keypad state)) :height (gwee:view-height (keypad state)))))
          (exp:log experiment 'submit :user-value input :correct correct)
          ;; ditch all the state's timers
          (exp:cancel-state-timers experiment state)
          (gwee:run-with-timer (feedback-timeout state) nil 'exp:state-onward experiment state))
        (progn
          (exp:log experiment 'input-submit :state (intern (symbol-name phase) :keyword) :user-value input)))))

(exp:define-action times-up ((state trial)
                             (exp:timeout))
  ;; FIXME: this could look nicer
  (setf (gwee:view-subviews (layout state))
        (list 
         (draw-feedback experiment state :timeout)
         (make-instance 'gwee:text
                        :font (gwee:open-font "serif.ttf" 48)
                        :height 45
                        :data "time's up"
                        :foreground (gwee:color 0 0 255))
         ;; we need a dummy view to keep the view as similar as possible
         (gwee:run-with-timer (feedback-timeout state) nil 'exp:state-onward experiment state)))
  (exp:log experiment 'timeout :user-value (input-widget:input-string (input state)))
  (gwee:run-with-timer (feedback-timeout state) nil 'exp:state-onward experiment state))
